package com.yingsheng.bi.hks.template.processor.impl;

import com.yingsheng.bi.hks.dto.Element;
import com.yingsheng.bi.hks.template.events.AdviceEvent;
import com.yingsheng.bi.hks.template.events.Event;
import com.yingsheng.bi.hks.template.processor.InnerProcessor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class DtoElementProcessor extends InnerProcessor {

    protected final Log logger = LogFactory.getLog(this.getClass());

    private final Set<Class<? extends Annotation>> annotationTypes = new LinkedHashSet(4);

    private final Map<Class, List<FieldElement>> cache = new ConcurrentHashMap();

    private final Map<Class, Object> cache2 = new ConcurrentHashMap();

    public DtoElementProcessor() {
        this.annotationTypes.add(Element.class);
    }

    @Override
    public void execInner(Event event) {
        if (event instanceof AdviceEvent) {
            AdviceEvent adviceEvent = (AdviceEvent) event;
            Object dto = adviceEvent.getInput();
            if (dto != null && !BeanUtils.isSimpleProperty(dto.getClass())) {
                List<FieldElement> elementFields = getElementFields(dto);
                if (!CollectionUtils.isEmpty(elementFields)) {
                    DtoElement dtoElement = new DtoElement(dto);
                    elementFields.forEach((fe) -> {
                        dtoElement.setField(fe);
                        adviceEvent.setInput(dtoElement);
                        super.execInner(adviceEvent);
                        adviceEvent.setInput(null);
                    });
                }
            }
        } else {
            super.execInner(event);
        }
    }

    private List<FieldElement> getElementFields(Object object) {
        if (object != null) {
            Class clazz = object.getClass();
            List<FieldElement> fieldElements = this.cache.get(clazz);
            if (fieldElements == null && this.cache2.get(clazz) == null) {
                synchronized (clazz) {
                    fieldElements = this.cache.get(clazz);
                    if (fieldElements == null && this.cache2.get(clazz) == null) {
                        fieldElements = detectField(clazz);
                        this.cache.put(clazz, fieldElements);
                        this.cache2.remove(clazz);
                    }
                }
            }
            return fieldElements;
        }
        return null;
    }

    private List<FieldElement> detectField(Class clazz) {
        List<FieldElement> list = new ArrayList<>();
        Class targetClass = clazz;
        do {
            ReflectionUtils.doWithLocalFields(targetClass, (field) -> {
                AnnotationAttributes attributes = this.getAnnotationAttributes(field);
                if (attributes != null) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        if (this.logger.isInfoEnabled()) {
                            this.logger.info("Annotation is not supported on static fields: " + field);
                        }
                        return;
                    }
                    String name = attributes.getString("name");
                    String[] attrs = attributes.getStringArray("attrs");
                    list.add(new FieldElement(field, name, attrs));
                }
            });
            targetClass = targetClass.getSuperclass();
        } while (targetClass != null && targetClass != Object.class);
        return list;
    }


    private AnnotationAttributes getAnnotationAttributes(AnnotatedElement element) {
        return AnnotatedElementUtils.getMergedAnnotationAttributes(element, Element.class);
    }

    private MergedAnnotation<?> findAnnotation(AccessibleObject ao) {
        MergedAnnotations annotations = MergedAnnotations.from(ao);
        Iterator var3 = this.annotationTypes.iterator();

        MergedAnnotation annotation;
        do {
            if (!var3.hasNext()) {
                return null;
            }
            Class<? extends Annotation> type = (Class) var3.next();
            annotation = annotations.get(type);
        } while (!annotation.isPresent());

        return annotation;
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE + 5;
    }

}
